﻿using ServiceStack.Redis;
using ShoppingCart.WebApi.Models;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Web;

namespace ShoppingCart.WebApi.DataLayer
{
    public class RedisRepository
    {
        RedisClient redisClient; 

        public RedisRepository()
        {
            var redisServer = DataHelper.GetConfigurationSetting("RedisServerName");
            var redisPort = int.Parse(DataHelper.GetConfigurationSetting("RedisServerPort"));
            redisClient = new RedisClient(redisServer, redisPort);
        }
        
        public void AddShoppingCart(ShoppingCartModel cart) 
        {
            //delete old cart, but don't check to make sure it already exists
            DeleteShoppingCart(cart.userId, false);

            var prodIds = new List<String>();
            //add shopping cart item
            foreach (var item in cart.items)
            {
                UpdateItem(cart.userId, item, false);
                prodIds.Add(item.prodId);
            }

            //add shopping cart
            redisClient.AddRangeToList(cart.userId, prodIds);
        }

        public ShoppingCartModel GetShoppingCart(String userId)
        {
            ShoppingCartModel shoppingCart = new ShoppingCartModel();
            shoppingCart.userId = userId;
            
            //get item list
            var prodIds = redisClient.GetRangeFromList(userId, 0, -1);
            if (prodIds != null && prodIds.Count>0)
            {                
                shoppingCart.totalValue = 0;
                shoppingCart.items = new List<ShoppingCartItem>();
                foreach (var prodId in prodIds) {
                    //get from shopping cart item
                    var item = GetShoppingCartItem(userId, prodId);
                    item.prodId = prodId;
                    shoppingCart.totalValue += item.prodPrice * item.quantity;
                    shoppingCart.items.Add(item);
                }               

                return shoppingCart;
            }
            else
            {
                return null;
            }
        }
        
        public void DeleteShoppingCart(string userId, bool checkExist=true)
        {
            //check if the shopping cart exists
            if (checkExist && redisClient.Exists(userId) == 0)
                throw new ShoppingCartException("Not Found");
            
            //get & remove all existing items from the cart if there was one found
            var oldCart = GetShoppingCart(userId);
            if (oldCart !=null && oldCart.items != null)
                foreach (var item in oldCart.items)
                {
                    var itemKey = GetItemKey(userId, item.prodId);
                    redisClient.Del(itemKey);
                }

            //remove existing shopping cart
            redisClient.Del(userId);
        }

        public void AddShoppingCartItem(String userId, ShoppingCartItem item)
        {
            String itemHName = GetItemKey(userId, item.prodId);
                        
            //RC: why does it get the existing quantity? why not just check if that item exists by its prodId?
            //    doesn't this check if the item exists >> if (redisClient.Exists(GetItemKey(userId, prodId)) == 0), or what does that do?

            ////get existing  item
            //var existingQuantity = redisClient.HGet(itemHName, Encoding.Default.GetBytes("quantity"));
            //if (existingQuantity != null)
            //{
            //    //RC: and if the item exists, why would you want to increment the value and not just overwrite it?
            //    //update existing item 
            //    int value = Convert.ToInt32(Encoding.Default.GetString(existingQuantity));
            //    item.quantity += value;
            //}

            //RC: what does this boolean switch do? why would i ever call UpdateItem and NOT update the cart?
            UpdateItem(userId, item, true);
        }

        //RC: nothing calls this method? the Put, which should be doing an update calls the AddShoppingCartItem method, why?
        public void UpdateShoppingCartItem(String userId, ShoppingCartItem item)
        {
            String itemHName = GetItemKey(userId, item.prodId);

            //get existing  item
            var existingQuantity = redisClient.HGet(itemHName, Encoding.Default.GetBytes("quantity"));
            if (existingQuantity == null)
                throw new ShoppingCartException("No matching record");

            //set item
            UpdateItem(userId, item, true);
        }

        public void DeleteShoppingCartItem(String userId, String prodId)
        {
            var itemKey = GetItemKey(userId, prodId);

            if (userId == null || prodId == null)
                throw new ShoppingCartException("Wrong Input");

            //check if the shopping cart item exist
            if (redisClient.Exists(itemKey) == 0)
                throw new ShoppingCartException("Not Found");

            //RC: what is this deleting? The cart item?
            redisClient.Del(itemKey);

            var prodIds = redisClient.GetRangeFromList(userId, 0, -1);
            prodIds.Remove(prodId);
            
            //RC: what is this Del doing? the whole cart?
            redisClient.Del(userId);

            //RC: now we appear to be readding a range of products? I don't understand this method. Please explain.
            //add shopping cart
            redisClient.AddRangeToList(userId, prodIds);
        }
        
        private ShoppingCartItem GetShoppingCartItem(String userId, String prodId)
        {
            string itemKey = GetItemKey(userId, prodId);
            //if (redisClient.Exists(GetItemKey(userId, prodId)) == 0)
            //    throw new ShoppingCartException("Wrong Input");

            // Try to get from cache first.
            ShoppingCartItem item = null;
          
            if (item == null)
            {
                item = new ShoppingCartItem();

                byte[][] fieldgroup = new byte[5][];
                fieldgroup[0] = Encoding.Default.GetBytes("imageUrl");
                fieldgroup[1] = Encoding.Default.GetBytes("prodDesc");
                fieldgroup[2] = Encoding.Default.GetBytes("prodName");
                fieldgroup[3] = Encoding.Default.GetBytes("prodPrice");
                fieldgroup[4] = Encoding.Default.GetBytes("quantity");

                //get hash value
                byte[][] valuegroup;
                try
                {
                    valuegroup = redisClient.HMGet(itemKey, fieldgroup);
                }
                catch (Exception)
                {
                    throw new ShoppingCartException(
                        string.Format("Unable to retrieve shopping cart item for userId {0} and prodId {}.", userId, prodId));
                }

                item.imageUrl = Encoding.Default.GetString(valuegroup[0]);
                item.prodDesc = Encoding.Default.GetString(valuegroup[1]);
                item.prodName = Encoding.Default.GetString(valuegroup[2]);
                item.prodPrice = Convert.ToDouble(Encoding.Default.GetString(valuegroup[3]));
                item.quantity = Convert.ToInt32(Encoding.Default.GetString(valuegroup[4]));
                item.prodId = prodId;
            }

            return item;
        }

        private void UpdateItem(String userId, ShoppingCartItem item, bool updateShoppingCart = false)
        {
            //set item
            byte[][] fieldgroup = new byte[5][];
            byte[][] valuegroup = new byte[5][];

            fieldgroup[0] = Encoding.Default.GetBytes("imageUrl");
            valuegroup[0] = Encoding.Default.GetBytes(item.imageUrl);
            fieldgroup[1] = Encoding.Default.GetBytes("prodDesc");
            valuegroup[1] = Encoding.Default.GetBytes(item.prodDesc);
            fieldgroup[2] = Encoding.Default.GetBytes("prodName");
            valuegroup[2] = Encoding.Default.GetBytes(item.prodName);
            fieldgroup[3] = Encoding.Default.GetBytes("prodPrice");
            valuegroup[3] = Encoding.Default.GetBytes("" + item.prodPrice);
            fieldgroup[4] = Encoding.Default.GetBytes("quantity");
            valuegroup[4] = Encoding.Default.GetBytes("" + item.quantity);

            var itemKey = GetItemKey(userId, item.prodId);
            redisClient.HMSet(itemKey, fieldgroup, valuegroup);

            if (updateShoppingCart)
            {
                //add item to shopping cart
                var prodIds = redisClient.GetRangeFromList(userId, 0, -1);
                prodIds.Remove(item.prodId);
                prodIds.Add(item.prodId);

                redisClient.Del(userId);

                //add shopping cart
                redisClient.AddRangeToList(userId, prodIds);
            }
        }

        private String GetItemKey(String userId, String prodId)
        {
            return userId + ":" + prodId;
        }
    }
}